package com.fm.structure.hash;

import java.util.Objects;

/**
 * @param <E> element type
 * @author finalmachine
 */
public abstract class AbstractHashTable<E> {
    protected interface HashNode<E> {
        E getElement();

        E setElement(E element);

        HashNode<E> getNext();

        void setNext(HashNode<E> next);
    }

    protected interface HashNodeProcessor<E, T> {
        /**
         * process when find the hash node
         *
         * @param node node found
         * @param key  use this key find the node
         * @return any thing you want
         */
        T process(HashNode<E> node, E key);

        /**
         * return value when not find
         *
         * @return any thing you want
         */
        T notFind();
    }

    protected int size = 0;
    protected int threshold;
    protected float loadFactor;
    protected float expandFactor;
    protected HashNode<E>[] table;

    abstract protected int hash(E element);

    abstract protected void reHash();

    abstract protected HashNode<E> createHashNode(E e);

    protected int render(int a, int b) {
        a = a % b;
        return a < 0 ? (a + b) : a;
    }

    protected <T> T findAndModify(E e, HashNodeProcessor<E, T> processor) {
        int hash = hash(e);
        int index;
        HashNode<E> node = table[index = render(hash, table.length)];
        if (node == null) {
            table[index] = createHashNode(e);
        } else {
            while (true) {
                if (hash == hash(node.getElement())) {
                    E cur = node.getElement();
                    if (Objects.equals(cur, e)) {
                        return processor.process(node, e);
                    }
                }
                if (node.getNext() != null) {
                    node = node.getNext();
                } else {
                    break;
                }
            }
            node.setNext(createHashNode(e));
        }
        if (++size > threshold) {
            reHash();
        }
        return processor.notFind();
    }

    protected HashNode<E> getNode(E key) {
        int hash = hash(key);
        HashNode<E> node = table[render(hash, table.length)];
        if (node != null) {
            while (node != null) {
                if (hash == hash(node.getElement()) && Objects.equals(node.getElement(), key)) {
                    return node;
                }
                node = node.getNext();
            }
        }
        return null;
    }

    protected HashNode<E> removeNode(E key) {
        int hash = hash(key);
        int index = render(hash(key), table.length);
        HashNode<E> node = table[index];
        if (node != null) {
            HashNode<E> preEntry = null;
            while (node != null) {
                if (hash == hash(node.getElement()) && Objects.equals(node.getElement(), key)) {
                    if (preEntry == null) {
                        table[index] = node.getNext();
                    } else {
                        preEntry.setNext(node.getNext());
                    }
                    size--;
                    return node;
                }
                preEntry = node;
                node = node.getNext();
            }
        }
        return null;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }
}
